package com.android.gl2jni.encoder;
/*
 * AudioVideoRecordingSample
 * Sample project to cature audio and video from internal mic/camera and save as MPEG4 file.
 *
 * Copyright (c) 2014-2015 saki t_saki@serenegiant.com
 *
 * File name: MediaAudioEncoder.java
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 * All files in the folder are under this Apache License, Version 2.0.
 */

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
import net.surina.soundtouch.SoundTouch;

public class MediaAudioEncoder extends MediaEncoder {

  private static final boolean DEBUG = false;  // TODO set false on release
  private static final String TAG = "MediaAudioEncoder";

  private static final String MIME_TYPE = "audio/mp4a-latm";
  private static final int SAMPLE_RATE = 44100;  // 44.1[KHz] is only setting guaranteed to be available on all devices.
  private static final int BIT_RATE = 64000;
  private static final int CHANNELS = 1;
  public static final int SAMPLES_PER_FRAME = 1024;  // AAC, bytes/frame/channel
  public static final int FRAMES_PER_BUFFER = 25;  // AAC, frame/buffer/sec

  private AudioThread mAudioThread = null;
  private SoundTouch soundTouch;

  public MediaAudioEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) {
    super(muxer, listener);
    soundTouch = new SoundTouch(SAMPLE_RATE, CHANNELS);
    soundTouch.setTempo(2f);
  }

  @Override
  protected void prepare() throws IOException {
    if (DEBUG) {
      Log.v(TAG, "prepare:");
    }
    mTrackIndex = -1;
    mMuxerStarted = mIsEOS = false;
    // prepare MediaCodec for AAC encoding of audio data from internal mic.
    final MediaCodecInfo audioCodecInfo = selectAudioCodec(MIME_TYPE);
    if (audioCodecInfo == null) {
      Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
      return;
    }
    if (DEBUG) {
      Log.i(TAG, "selected codec: " + audioCodecInfo.getName());
    }

    final MediaFormat audioFormat = MediaFormat.createAudioFormat(MIME_TYPE, SAMPLE_RATE, 1);
    audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    audioFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
    audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
    audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
//		audioFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, inputFile.length());
//      audioFormat.setLong(MediaFormat.KEY_DURATION, (long)durationInMs );
    if (DEBUG) {
      Log.i(TAG, "format: " + audioFormat);
    }
    mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
    mMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    mMediaCodec.start();
    if (DEBUG) {
      Log.i(TAG, "prepare finishing");
    }
    if (mListener != null) {
      try {
        mListener.onPrepared(this);
      } catch (final Exception e) {
        Log.e(TAG, "prepare:", e);
      }
    }
  }

  @Override
  protected void startRecording() {
    super.startRecording();
    // create and execute audio capturing thread using internal mic
    if (mAudioThread == null) {
      mAudioThread = new AudioThread();
      mAudioThread.start();
    }
  }

  @Override
  protected void release() {
    mAudioThread = null;
    super.release();
  }

  private static final int[] AUDIO_SOURCES = new int[]{
      MediaRecorder.AudioSource.MIC,
      MediaRecorder.AudioSource.DEFAULT,
      MediaRecorder.AudioSource.CAMCORDER,
      MediaRecorder.AudioSource.VOICE_COMMUNICATION,
      MediaRecorder.AudioSource.VOICE_RECOGNITION,
  };

  /**
   * Thread to capture audio data from internal mic as uncompressed 16bit PCM data
   * and write them to the MediaCodec encoder
   */
  private class AudioThread extends Thread {

    @Override
    public void run() {
      android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
      try {
        final int min_buffer_size = AudioRecord.getMinBufferSize(
            SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT);
        int buffer_size = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER;
        if (buffer_size < min_buffer_size) {
          buffer_size = ((min_buffer_size / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2;
        }

        AudioRecord audioRecord = null;
        for (final int source : AUDIO_SOURCES) {
          try {
            audioRecord = new AudioRecord(
                source, SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, buffer_size);
            if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
              audioRecord = null;
            }
          } catch (final Exception e) {
            audioRecord = null;
          }
          if (audioRecord != null) {
            break;
          }
        }
        if (audioRecord != null) {
          try {
            if (mIsCapturing) {
              if (DEBUG) {
                Log.v(TAG, "AudioThread:start audio recording");
              }
              byte[] in = new byte[SAMPLES_PER_FRAME];
              //final ByteBuffer outbuf = ByteBuffer.allocateDirect(SAMPLES_PER_FRAME);
              byte[] out = new byte[SAMPLES_PER_FRAME];
              int readBytes;
              audioRecord.startRecording();
              try {
                for (; mIsCapturing && !mRequestStop && !mIsEOS; ) {
                  // read audio data from internal mic
                  readBytes = audioRecord.read(in, 0, SAMPLES_PER_FRAME);
                  Log.d(TAG, "------->readBytes:" + readBytes);
                  if (readBytes > 0) {
                    // set audio data to encoder
                    if (soundTouch != null) {
                      soundTouch.putBytes(in, readBytes);
                      //int read = soundTouch.processData(in, readBytes, out, SAMPLES_PER_FRAME, CHANNELS);
                      while (true) {
                        int read = soundTouch.getBytes(out, SAMPLES_PER_FRAME);
                        if (read > 0) {
                          Log.d(TAG, "-------------->encode2:" + read);
                          encode2(out, read, getPTSUs());
                          frameAvailableSoon();
                        } else {
                          break;
                        }
                      }
                    } else {
//                      outbuf.clear();
//                      outbuf.put(in, 0, readBytes);
//                      outbuf.position(readBytes);
//                      outbuf.flip();
                      encode2(in, readBytes, getPTSUs());
                      frameAvailableSoon();
                    }
                  }
                }
                frameAvailableSoon();
              } finally {
                audioRecord.stop();
              }
            }
          } finally {
            audioRecord.release();
          }
        } else {
          Log.e(TAG, "failed to initialize AudioRecord");
        }
      } catch (final Exception e) {
        Log.e(TAG, "AudioThread#run", e);
      }
      if (DEBUG) {
        Log.v(TAG, "AudioThread:finished");
      }
    }
  }

  /**
   * select the first codec that match a specific MIME type
   */
  private static MediaCodecInfo selectAudioCodec(final String mimeType) {
    if (DEBUG) {
      Log.v(TAG, "selectAudioCodec:");
    }

    MediaCodecInfo result = null;
    // get the list of available codecs
    final int numCodecs = MediaCodecList.getCodecCount();
    LOOP:
    for (int i = 0; i < numCodecs; i++) {
      final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
      if (!codecInfo.isEncoder()) {  // skipp decoder
        continue;
      }
      final String[] types = codecInfo.getSupportedTypes();
      for (String type : types) {
        if (DEBUG) {
          Log.i(TAG, "supportedType:" + codecInfo.getName() + ",MIME=" + type);
        }
        if (type.equalsIgnoreCase(mimeType)) {
          result = codecInfo;
          break LOOP;
        }
      }
    }
    return result;
  }

}
